﻿using System;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;

namespace GDExtensions
{
    /// <summary>
    /// Extension methods for the <see cref="T:Microsoft.SharePoint.SPItemEventProperties"/> class.
    /// </summary>
    /// <remarks></remarks>
    public static class SPItemEventPropertiesExtensions
    {
        #region Public Methods
        /// <summary>
        /// Method to get the <see cref="Microsoft.SharePoint.SPList"/> field by the display or internal name.
        /// </summary>
        /// <param name="properties">The <see cref="T:Microsoft.SharePoint.SPItemEventProperties"/> properties.</param>
        /// <param name="fieldName">The <see cref="T:Microsoft.SharePoint.SPField"/> field name.</param>
        /// <returns>The <see cref="T:Microsoft.SharePoint.SPField"/> field if it exists, else NULL.</returns>
        /// <remarks></remarks>
        public static SPField GetField(this SPItemEventProperties properties, string fieldName)
        {
            // Call the private method
            return _GetField(properties, fieldName);
        }

        /// <summary>
        /// Method to get the <see cref="T:Microsoft.SharePoint.SPListItem"/> field value.
        /// </summary>
        /// <param name="properties">The <see cref="T:Microsoft.SharePoint.SPItemEventProperties"/> properties.</param>
        /// <param name="fieldName">The <see cref="T:Microsoft.SharePoint.SPField"/> field name.</param>
        /// <returns>The <see cref="T:Microsoft.SharePoint.SPListItem"/> <paramref name="fieldName"/> value.</returns>
        /// <remarks></remarks>
        public static string GetFieldValue(this SPItemEventProperties properties, string fieldName)
        {
            // Return the field value
            return properties.GetFieldValue(properties.GetField(fieldName), false);
        }

        /// <summary>
        /// Method to get the <see cref="T:Microsoft.SharePoint.SPListItem"/> field value.
        /// </summary>
        /// <param name="properties">The <see cref="T:Microsoft.SharePoint.SPItemEventProperties"/> properties.</param>
        /// <param name="fieldName">The <see cref="T:Microsoft.SharePoint.SPField"/> field name.</param>
        /// <param name="returnCurrentValue">if set to <c>true</c> returns the current <see cref="T:Microsoft.SharePoint.SPListItem"/> value.</param>
        /// <returns>The <see cref="T:Microsoft.SharePoint.SPListItem"/> <paramref name="fieldName"/> value.</returns>
        /// <remarks></remarks>
        public static string GetFieldValue(this SPItemEventProperties properties, string fieldName, bool returnCurrentValue)
        {
            // Return the field value
            return properties.GetFieldValue(properties.GetField(fieldName), returnCurrentValue);
        }

        /// <summary>
        /// Method to get the <see cref="T:Microsoft.SharePoint.SPListItem"/> field value.
        /// </summary>
        /// <param name="properties">The <see cref="T:Microsoft.SharePoint.SPItemEventProperties"/> properties.</param>
        /// <param name="field">The <see cref="T:Microsoft.SharePoint.SPField"/> field.</param>
        /// <returns>The <see cref="T:Microsoft.SharePoint.SPListItem"/> <paramref name="fieldName"/> value.</returns>
        /// <remarks></remarks>
        public static string GetFieldValue(this SPItemEventProperties properties, SPField field)
        {
            // Return the field value
            return properties.GetFieldValue(field, false);
        }

        /// <summary>
        /// Method to get the <see cref="T:Microsoft.SharePoint.SPListItem"/> field value.
        /// </summary>
        /// <param name="properties">The <see cref="T:Microsoft.SharePoint.SPItemEventProperties"/> properties.</param>
        /// <param name="field">The <see cref="T:Microsoft.SharePoint.SPField"/> field.</param>
        /// <param name="returnCurrentValue">if set to <c>true</c> returns the current <see cref="T:Microsoft.SharePoint.SPListItem"/> value.</param>
        /// <returns>The <see cref="T:Microsoft.SharePoint.SPListItem"/> <paramref name="fieldName"/> value.</returns>
        /// <remarks></remarks>
        public static string GetFieldValue(this SPItemEventProperties properties, SPField field, bool returnCurrentValue)
        {
            // Call the private method
            return _GetFieldValue(properties, field, returnCurrentValue);
        }

        /// <summary>
        /// Determines whether the <see cref="T:Microsoft.SharePoint.SPField"/> field value is changing.
        /// </summary>
        /// <param name="properties">The <see cref="T:Microsoft.SharePoint.SPItemEventProperties"/> properties.</param>
        /// <param name="fieldName">The <see cref="T:Microsoft.SharePoint.SPField"/> field name.</param>
        /// <returns><c>true</c> if [is field value changing] [the specified properties]; otherwise, <c>false</c>.</returns>
        /// <remarks></remarks>
        public static bool IsFieldValueChanging(this SPItemEventProperties properties, string fieldName)
        {
            // See if the field value is changing
            return properties.IsFieldValueChanging(properties.GetField(fieldName), false);
        }

        /// <summary>
        /// Determines whether the <see cref="T:Microsoft.SharePoint.SPField"/> field value is changing.
        /// </summary>
        /// <param name="properties">The <see cref="T:Microsoft.SharePoint.SPItemEventProperties"/> properties.</param>
        /// <param name="fieldName">The <see cref="T:Microsoft.SharePoint.SPField"/> field name.</param>
        /// <param name="caseSensativeFl">The case sensativity flag.</param>
        /// <returns><c>true</c> if <paramref name="fieldName"/> value is being updated; otherwise, <c>false</c>.</returns>
        /// <remarks></remarks>
        public static bool IsFieldValueChanging(this SPItemEventProperties properties, string fieldName, bool caseSensativeFl)
        {
            // See if the field value is changing
            return properties.IsFieldValueChanging(properties.GetField(fieldName), caseSensativeFl);
        }

        /// <summary>
        /// Determines whether the <see cref="T:Microsoft.SharePoint.SPField"/> field value is changing.
        /// </summary>
        /// <param name="properties">The <see cref="T:Microsoft.SharePoint.SPItemEventProperties"/> properties.</param>
        /// <param name="field">The <see cref="T:Microsoft.SharePoint.SPField"/> field.</param>
        /// <returns><c>true</c> if <paramref name="fieldName"/> value is being updated; otherwise, <c>false</c>.</returns>
        /// <remarks></remarks>
        public static bool IsFieldValueChanging(this SPItemEventProperties properties, SPField field)
        {
            // See if the field value is changing
            return properties.IsFieldValueChanging(field, false);
        }

        /// <summary>
        /// Determines whether the <see cref="T:Microsoft.SharePoint.SPField"/> field value is changing.
        /// </summary>
        /// <param name="properties">The <see cref="T:Microsoft.SharePoint.SPItemEventProperties"/> properties.</param>
        /// <param name="field">The <see cref="T:Microsoft.SharePoint.SPField"/> field.</param>
        /// <param name="caseSensativeFl">The case sensativity flag.</param>
        /// <returns><c>true</c> if <paramref name="fieldName"/> value is being updated; otherwise, <c>false</c>.</returns>
        /// <remarks></remarks>
        public static bool IsFieldValueChanging(this SPItemEventProperties properties, SPField field, bool caseSensativeFl)
        {
            // Call the private method
            return _IsFieldValueChanging(properties, field, caseSensativeFl);
        }

        /// <summary>
        /// Method to determine if a field being added is unique to the list.
        /// </summary>
        /// <param name="properties">The <see cref="T:Microsoft.SharePoint.SPItemEventProperties"/> properties.</param>
        /// <param name="fieldName">The <see cref="T:Microsoft.SharePoint.SPField"/> field name.</param>
        /// <returns><c>true</c> if <paramref name="fieldName"/>'s value is unique; otherwise, <c>false</c>.</returns>
        /// <remarks></remarks>
        public static bool IsUniqueValue(this SPItemEventProperties properties, string fieldName)
        {
            // Call the is unique value method
            return properties.IsUniqueValue(properties.GetField(fieldName));
        }

        /// <summary>
        /// Method to determine if a field being added is unique to the list.
        /// </summary>
        /// <param name="properties">The <see cref="T:Microsoft.SharePoint.SPItemEventProperties"/> properties.</param>
        /// <param name="field">The <see cref="T:Microsoft.SharePoint.SPField"/> field.</param>
        /// <returns><c>true</c> if <paramref name="fieldName"/>'s value is unique; otherwise, <c>false</c>.</returns>
        /// <remarks></remarks>
        public static bool IsUniqueValue(this SPItemEventProperties properties, SPField field)
        {
            // Call the private method
            return _IsUniqueValue(properties, field);
        }

        /// <summary>
        /// Method to see if a field is being updated
        /// </summary>
        /// <param name="properties">The <see cref="T:Microsoft.SharePoint.SPItemEventProperties"/> properties.</param>
        /// <returns><c>true</c> if the <paramref name="properties"/> has a field being updated; otherwise, <c>false</c>.</returns>
        /// <remarks></remarks>
        public static bool IsUpdateRequired(this SPItemEventProperties properties)
        {
            // Call the IsUpdateRequired method, ignoring the case
            return properties.IsUpdateRequired(false);
        }

        /// <summary>
        /// Method to see if a field is being updated
        /// </summary>
        /// <param name="properties">The <see cref="T:Microsoft.SharePoint.SPItemEventProperties"/> properties.</param>
        /// <param name="caseSensativeFl">The case sensativity flag.</param>
        /// <returns><c>true</c> if the <paramref name="properties"/> has a field being updated; otherwise, <c>false</c>.</returns>
        /// <remarks></remarks>
        public static bool IsUpdateRequired(this SPItemEventProperties properties, bool caseSensativeFl)
        {
            // Call the private method
            return _IsUpdateRequired(properties, caseSensativeFl);
        }

        /// <summary>
        /// Method to trim leading/trailing spaces from the values
        /// </summary>
        /// <param name="properties">The <see cref="T:Microsoft.SharePoint.SPItemEventProperties"/> properties.</param>
        /// <remarks></remarks>
        public static void TrimSpaces(this SPItemEventProperties properties)
        {
            // Call the private method
            _TrimSpaces(properties);
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Method to get the <see cref="Microsoft.SharePoint.SPList"/> field by the display or internal name.
        /// </summary>
        /// <param name="properties">The <see cref="T:Microsoft.SharePoint.SPItemEventProperties"/> properties.</param>
        /// <param name="fieldName">The <see cref="T:Microsoft.SharePoint.SPField"/> field name.</param>
        /// <returns>The <see cref="T:Microsoft.SharePoint.SPField"/> field if it exists, else NULL.</returns>
        /// <remarks></remarks>
        private static SPField _GetField(SPItemEventProperties properties, string fieldName)
        {
            SPField field = default(SPField);

            // Ensure the list exists
            if (properties.List != null)
            {
                // Get the field
                field = properties.List.GetField(fieldName);
            }

            // Return the field
            return field;
        }

        /// <summary>
        /// Method to get the <see cref="T:Microsoft.SharePoint.SPListItem"/> field value.
        /// </summary>
        /// <param name="properties">The <see cref="T:Microsoft.SharePoint.SPItemEventProperties"/> properties.</param>
        /// <param name="field">The <see cref="T:Microsoft.SharePoint.SPField"/> field.</param>
        /// <param name="returnCurrentValue">if set to <c>true</c> returns the current <see cref="T:Microsoft.SharePoint.SPListItem"/> value.</param>
        /// <returns>The <see cref="T:Microsoft.SharePoint.SPListItem"/> <paramref name="fieldName"/> value.</returns>
        /// <remarks></remarks>
        private static string _GetFieldValue(SPItemEventProperties properties, SPField field, bool returnCurrentValue)
        {
            string fieldValue = string.Empty;

            // Ensure the field exists
            if (field != null)
            {
                // Try to get the field value
                try
                {
                    // See if we are updating this field
                    if (properties.AfterProperties[field.InternalName] != null)
                    {
                        // Set the value to the new value
                        fieldValue = properties.AfterProperties[field.InternalName] + "";

                        // See if we are returning the current value, and a list item exists
                        if (returnCurrentValue && properties.ListItem != null)
                        {
                            // Set the value to the current value
                            fieldValue = properties.ListItem[field.Id] + "";
                        }
                    }
                    // Else, see if a list item exists
                    else if (properties.ListItem != null)
                    {
                        // Set the value to the current value
                        fieldValue = properties.ListItem[field.Id] + "";
                    }
                }
                catch { }
            }

            // Return the field value
            return fieldValue;
        }

        /// <summary>
        /// Determines whether the <see cref="T:Microsoft.SharePoint.SPField"/> field value is changing.
        /// </summary>
        /// <param name="properties">The <see cref="T:Microsoft.SharePoint.SPItemEventProperties"/> properties.</param>
        /// <param name="field">The <see cref="T:Microsoft.SharePoint.SPField"/> field.</param>
        /// <param name="caseSensativeFl">The case sensativity flag.</param>
        /// <returns><c>true</c> if <paramref name="fieldName"/> value is being updated; otherwise, <c>false</c>.</returns>
        /// <remarks></remarks>
        private static bool _IsFieldValueChanging(SPItemEventProperties properties, SPField field, bool caseSensativeFl)
        {
            // Ensure this is not a new item
            if (properties.ListItem == null) { return true; }

            // See if the after properties contains a new value
            if (properties.AfterProperties[field.InternalName] != null)
            {
                string oldValue = properties.ListItem == null ? "" : properties.ListItem[field.Id] + "";
                string newValue = properties.AfterProperties[field.InternalName] + "";

                // See if this is a date/time field
                if (field.Type == SPFieldType.DateTime)
                {
                    // Convert the values to datetime objects
                    DateTime oldDT = ConvertToDateTime(field, oldValue);
                    DateTime newDT = ConvertToDateTime(field, newValue);

                    // Compare the dates
                    return !oldDT.Equals(newDT);
                }

                // See if we are testing for case sensative information
                if (caseSensativeFl && oldValue != newValue)
                {
                    // Update is required
                    return true;
                }
                // Else, ignore the case
                else if (!oldValue.Equals(newValue, StringComparison.InvariantCulture))
                {
                    // Update is required
                    return true;
                }
            }

            // Return false by default
            return false;
        }

        /// <summary>
        /// Method to determine if a field being added is unique to the list.
        /// </summary>
        /// <param name="properties">The <see cref="T:Microsoft.SharePoint.SPItemEventProperties"/> properties.</param>
        /// <param name="field">The <see cref="T:Microsoft.SharePoint.SPField"/> field.</param>
        /// <returns><c>true</c> if <paramref name="fieldName"/>'s value is unique; otherwise, <c>false</c>.</returns>
        /// <remarks></remarks>
        private static bool _IsUniqueValue(SPItemEventProperties properties, SPField field)
        {
            // Ensure the field exists
            if (field != null)
            {
                // Create a query for the list
                SPQuery query = new SPQuery()
                {
                    Query = "<Where><And>" +                        
                        "<Neq>" +
                            "<FieldRef Name=\"ID\" />" +
                            "<Value Type=\"Number\">" + properties.ListItemId + "</Value>" +
                        "</Neq>" +
                        "<Eq>" +
                            "<FieldRef Name=\"" + field.InternalName + "\" />" +
                            "<Value Type=\"" + field.TypeAsString + "\">" + SPEncode.HtmlEncode(properties.AfterProperties[field.InternalName] + "") + "</Value>" +
                        "</Eq>" +
                    "</And></Where>"
                };

                // Execute the query
                SPListItemCollection listItems = properties.List.GetItems(query);

                // See if this field value is unique
                // Count = 0 (Item is unique)
                return listItems.Count == 0;
            }

            // Return false by default
            return false;
        }

        /// <summary>
        /// Method to see if a field is being updated
        /// </summary>
        /// <param name="properties">The <see cref="T:Microsoft.SharePoint.SPItemEventProperties"/> properties.</param>
        /// <param name="caseSensativeFl">The case sensativity flag.</param>
        /// <returns><c>true</c> if the <paramref name="properties"/> has a field being updated; otherwise, <c>false</c>.</returns>
        /// <remarks></remarks>
        private static bool _IsUpdateRequired(SPItemEventProperties properties, bool caseSensativeFl)
        {
            // Ensure this is the item updating event
            if (properties.EventType == SPEventReceiverType.ItemUpdating)
            {
                // Parse the list fields
                foreach (SPField field in properties.List.Fields)
                {
                    // See if the field value is changing
                    if (properties.IsFieldValueChanging(field, caseSensativeFl))
                    {
                        return true;
                    }
                }
            }
            // This method should only be called for the item updating event
            else
            {
                // Return true if the event type is incorrect
                return true;
            }

            // Return false by default
            return false;
        }

        /// <summary>
        /// Method to trim leading/trailing spaces from the values
        /// </summary>
        /// <param name="properties">The <see cref="T:Microsoft.SharePoint.SPItemEventProperties"/> properties.</param>
        /// <remarks></remarks>
        private static void _TrimSpaces(this SPItemEventProperties properties)
        {
            // Ensure it's the adding/updating event
            if (properties.EventType == SPEventReceiverType.ItemAdding ||
                properties.EventType == SPEventReceiverType.ItemUpdating)
            {
                // Parse the list fields
                foreach (SPField field in properties.List.Fields)
                {
                    // See if we are updating the value
                    if (properties.AfterProperties[field.InternalName] != null)
                    {
                        // Trim the value
                        properties.AfterProperties[field.InternalName] =
                            (properties.AfterProperties[field.InternalName] + "").Trim();
                    }
                }
            }            
        }

        /// <summary>
        /// Method to convert a datetime string value to a datetime object.
        /// </summary>
        /// <param name="field">The datetime field.</param>
        /// <param name="value">The datetime value.</param>
        /// <returns>A datetime object.</returns>
        private static DateTime ConvertToDateTime(SPField field, string value)
        {
            DateTime dt = default(DateTime);

            // Ensure a value exists
            if (string.IsNullOrEmpty(value)) { return dt; }

            // Try to convert the value
            try
            {
                dt = SPUtility.ParseDate(field.ParentList.ParentWeb, value, SPDateFormat.DateTime, true);
            }
            // Format is ISO8601
            catch
            {
                // Convert the ISO8601 value
                if (DateTime.TryParseExact(value.Replace("Z", ""), "s", System.Globalization.CultureInfo.InvariantCulture,
                    System.Globalization.DateTimeStyles.RoundtripKind, out dt))
                {
                    // Convert the value again to ensure the time zone is corrected
                    dt = ConvertToDateTime(field, dt.ToString());
                }
            }

            return dt;
        }
        #endregion
    }
}
